package steveshrader.budget.client;

import java.util.Set;

import javax.validation.ConstraintViolation;

import steveshrader.budget.client.events.EditExpenseEvent;
import steveshrader.budget.client.events.ModifiedExpenseEvent;
import steveshrader.budget.client.widgets.ExpenseEditor;
import steveshrader.budget.shared.BudgetRequestFactory;
import steveshrader.budget.shared.BudgetRequestFactory.ExpenseRequest;
import steveshrader.budget.shared.ExpenseProxy;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.web.bindery.requestfactory.gwt.client.RequestFactoryEditorDriver;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.Request;
import com.google.web.bindery.requestfactory.shared.RequestContext;

/**
 * This class shows how the UI for editing an expense is wired up to the
 * RequestFactoryEditorDelegate. It is also responsible for showing and
 * dismissing the ExpenseEditor.
 */
public class ExpenseEditorWorkflow {
  interface Binder extends UiBinder<DialogBox, ExpenseEditorWorkflow> {
    Binder BINDER = GWT.create(Binder.class);
  }

  interface Driver extends
      RequestFactoryEditorDriver<ExpenseProxy, ExpenseEditor> {
  }

  static void register(final EventBus eventBus, final BudgetRequestFactory requestFactory, final FieldsHelper fieldsHelper) {
    eventBus.addHandler(EditExpenseEvent.TYPE, new EditExpenseEvent.Handler() {
      public void startEdit(ExpenseProxy expense, RequestContext requestContext) {
        new ExpenseEditorWorkflow(eventBus, requestFactory, fieldsHelper, expense).edit(requestContext);
      }
    });
  }

  @UiField
  HTMLPanel contents;

  @UiField
  DialogBox dialog;

  @UiField(provided = true)
  ExpenseEditor expenseEditor;

  private Driver editorDriver;
  private ExpenseProxy expense;
  private final EventBus eventBus;
  private final BudgetRequestFactory requestFactory;

  private ExpenseEditorWorkflow(EventBus eventBus, BudgetRequestFactory requestFactory, FieldsHelper fieldsHelper, ExpenseProxy expense) {
	this.eventBus = eventBus;
    this.requestFactory = requestFactory;
    this.expense = expense;
    expenseEditor = new ExpenseEditor(fieldsHelper);
    Binder.BINDER.createAndBindUi(this);
    contents.addDomHandler(new KeyUpHandler() {
      public void onKeyUp(KeyUpEvent event) {
        if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) {
          onCancel(null);
        }
      }
    }, KeyUpEvent.getType());
  }

  /**
   * Called by the cancel button when it is clicked. This method will just tear
   * down the UI and clear the state of the workflow.
   */
  @UiHandler("cancel")
  void onCancel(ClickEvent event) {
    dialog.hide();
  }

  /**
   * Called by the edit dialog's save button. This method will flush the
   * contents of the UI into the ExpenseProxy that is being edited, check for
   * errors, and send the request to the server.
   */
  @UiHandler("save")
  void onSave(ClickEvent event) {
    // Flush the contents of the UI
    RequestContext context = editorDriver.flush();

    // Check for errors
    if (editorDriver.hasErrors()) {
      dialog.setText("Errors detected locally");
      return;
    }

    // Send the request
    context.fire(new Receiver<Void>() {
      @Override
      public void onSuccess(Void response) {
        dialog.hide();
        eventBus.fireEvent(new ModifiedExpenseEvent());
      }

      @Override
      public void onConstraintViolation(Set<ConstraintViolation<?>> errors) {
        // Otherwise, show ConstraintViolations in the UI
    	StringBuilder errorString = new StringBuilder();
    	errorString.append("Errors detected on the server:<br/>");
    	for (ConstraintViolation<?> violation : errors) {
    		errorString.append(violation.getPropertyPath()).append(" ").append(violation.getMessage()).append("<br/>");
		}
        dialog.setHTML(errorString.toString());
      }
    });
  }

  /**
   * Called by the edit dialog's delete button.
   */
  @UiHandler("delete")
  void onDelete(ClickEvent event) {
	  if(expense == null || expense.getId() == null) {	//this was on a new expense not editing an existing one		  
		  dialog.hide();		  
	  }
	  else {
	      requestFactory.budgetServiceRequest().deleteExpense(expense).fire(
	            	new Receiver<String>() {
	            		@Override
	                    public void onSuccess(String response) {
	            			eventBus.fireEvent(new ModifiedExpenseEvent());		  	  
	      		  	  		dialog.hide();
	                    }
	                });
	  }
  }
  
  /**
   * Construct and display the UI that will be used to edit the current
   * ExpenseProxy, using the given RequestContext to accumulate the edits.
   */
  private void edit(RequestContext requestContext) {
    editorDriver = GWT.create(Driver.class);
    editorDriver.initialize(requestFactory, expenseEditor);
    
    if (requestContext == null) {
      fetchAndEdit();
      return;
    }

    editorDriver.edit(expense, requestContext);
    dialog.center();
  }

  private void fetchAndEdit() {
    // The request is configured arbitrarily
    Request<ExpenseProxy> fetchRequest = requestFactory.find(expense.stableId());

    // Add the paths that the EditorDrives computes
    fetchRequest.with(editorDriver.getPaths());

    // We could do more with the request, but we just fire it
    fetchRequest.to(new Receiver<ExpenseProxy>() {
      @Override
      public void onSuccess(ExpenseProxy expense) {
    	  ExpenseEditorWorkflow.this.expense = expense;
        // Start the edit process
        ExpenseRequest context = requestFactory.expenseRequest();
        // Display the UI
        edit(context);
        // Configure the method invocation to be sent in the context
        context.persist().using(expense);
        // The context will be fire()'ed from the onSave() method
      }
    }).fire();
  }
}
